home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2007 December / PCWKCD1207B.iso / Blogowanie poza sfera / Flock 1.0 beta / flock-1.0RC3.en-US.win32.exe / flock / components / flockPhotoAPIManager.js < prev    next >
Text File  |  2007-10-18  |  32KB  |  906 lines

  1. // BEGIN FLOCK GPL
  2. // 
  3. // Copyright Flock Inc. 2005-2007
  4. // http://flock.com
  5. // 
  6. // This file may be used under the terms of of the
  7. // GNU General Public License Version 2 or later (the "GPL"),
  8. // http://www.gnu.org/licenses/gpl.html
  9. // 
  10. // Software distributed under the License is distributed on an "AS IS" basis,
  11. // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12. // for the specific language governing rights and limitations under the
  13. // License.
  14. // 
  15. // END FLOCK GPL
  16.  
  17. const Cc = Components.classes;
  18. const Ci = Components.interfaces;
  19.  
  20. const FLOCK_PHOTO_API_MANAGER_CID               = Components.ID('{18e27f2e-c99c-4915-9907-1a9fc0780ed6}');
  21.  
  22. const nsISupports                   = Components.interfaces.nsISupports;
  23. const nsIClassInfo                  = Components.interfaces.nsIClassInfo;
  24. const nsIFactory                    = Components.interfaces.nsIFactory;
  25. const nsIProperties                 = Components.interfaces.nsIProperties;
  26. const nsILocalFile                  = Components.interfaces.nsILocalFile;
  27. const nsIFile                       = Components.interfaces.nsIFile;
  28. const nsIIOService                  = Components.interfaces.nsIIOService;
  29. const nsIFileProtocolHandler        = Components.interfaces.nsIFileProtocolHandler;
  30. const nsIPreferenceService          = Components.interfaces.nsIPrefBranch;
  31. const flockIPhotoAPIManager         = Components.interfaces.flockIPhotoAPIManager;
  32. const flockIPhotoPeopleService      = Components.interfaces.flockIPhotoPeopleService;
  33. const flockIPollingService          = Components.interfaces.flockIPollingService;
  34. const flockIMigratable              = Components.interfaces.flockIMigratable;
  35.  
  36. const FLOCK_PHOTO_API_MANAGER_CONTRACTID        = '@flock.com/photo-api-manager;1?';
  37. const FLOCK_PHOTO_CONTRACTID        = '@flock.com/photo;1';
  38. const DIRECTORY_SERVICE_CONTRACTID  = '@mozilla.org/file/directory_service;1';
  39. const LOCAL_FILE_CONTRACTID         = '@mozilla.org/file/local;1';
  40. const PREFERENCES_CONTRACTID        = '@mozilla.org/preferences-service;1';
  41. const IO_SERVICE_CONTRACTID         = '@mozilla.org/network/io-service;1';
  42. const RDFCU_CONTRACTID              = '@mozilla.org/rdf/container-utils;1';
  43. const MYWORLD_SERVICE_CONTRACTID    = '@flock.com/myworld-service;1';
  44.  
  45. const RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
  46. const FLOCK_NS = "http://flock.com/rdf#";
  47. const NC_NS = "http://home.netscape.com/NC-rdf#";
  48.  
  49. /* for Cardinal migration */
  50. const OLD_PEOPLE_PHOTO_RDF_FILE = 'flock_people_photo.rdf';
  51. const OLD_PEOPLE_PHOTO_RDF_FILE_RELIC = 'flock_people_photo_old.rdf';
  52. const LIST_ACCOUNTS_ROOT = 'urn:flock:people:photo:lists:accounts';
  53. const MEDIA_FAVES_ROOT = 'urn:media:favorites';
  54.  
  55. const RDFS = Components.classes['@mozilla.org/rdf/rdf-service;1']
  56.            .getService (Components.interfaces.nsIRDFService);
  57. const IOS = Components.classes[IO_SERVICE_CONTRACTID]
  58.           .getService(Components.interfaces.nsIIOService);
  59. const RDFCU = Components.classes[RDFCU_CONTRACTID]
  60.             .getService(Components.interfaces.nsIRDFContainerUtils);
  61.  
  62. function flockPhotoAPIManager() {
  63.     this.processingQueue = {};
  64.     this.mPrefService = Components.classes[PREFERENCES_CONTRACTID].getService(nsIPreferenceService);
  65.  
  66.     this.mLogger = Components.classes["@flock.com/logger;1"].createInstance(Components.interfaces.flockILogger);
  67.     this.mLogger.init("photo");
  68.     this.mLogger.info("starting up...");
  69.  
  70.     var inst = this;
  71.     var timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
  72.     var timerFunc = {
  73.         notify: function(aTimer) {
  74.             inst.updateStates();
  75.         },
  76.     }
  77.     timer.initWithCallback(timerFunc, 5 * 1000, 1);
  78. }
  79.  
  80. //flockIMigratable
  81.  
  82. flockPhotoAPIManager.prototype.__defineGetter__("migrationName",
  83. function getter_migrationName() {
  84.   return "Photo Accounts";
  85. });
  86.  
  87. flockPhotoAPIManager.prototype.needsMigration =
  88. function needsMigration(oldVersion) {
  89.   this.mLogger.info(".needsMigration('" + oldVersion + "')\n");
  90.   
  91.   if (oldVersion.substr(0, 3) == "0.7") { // migration from Cardinal (0.7.x)
  92.     var oldPeoplePhotoFile = Components.classes["@mozilla.org/file/directory_service;1"]
  93.                            .getService(Components.interfaces.nsIProperties)
  94.                            .get("ProfD", Components.interfaces.nsIFile);
  95.     oldPeoplePhotoFile.append(OLD_PEOPLE_PHOTO_RDF_FILE);
  96.  
  97.     if (oldPeoplePhotoFile.exists()) {
  98.       this.mLogger.info("needs migration from 0.7.x\n");
  99.       return true;
  100.     }
  101.     return false;
  102.   } else if (oldVersion.substr(0, 3) == "0.9") { // migration from 0.9.X
  103.     this.mLogger.info("needs migration from 0.9.X\n");
  104.     return true;
  105.   } else {
  106.     return false;
  107.   }
  108. }
  109.  
  110. flockPhotoAPIManager.prototype.startMigration =
  111. function startMigration(oldVersion, aFlockMigrationProgressListener) {
  112.   this.mLogger.info(".startMigration('" + oldVersion + "', 'aFlockMigrationProgressListener')\n");
  113.   var ctxt = {
  114.     oldVersion: oldVersion,
  115.     listener: aFlockMigrationProgressListener
  116.   };
  117.  
  118.   if (oldVersion.substr(0, 3) == "0.7") { // migration from Cardinal (0.7.x)
  119.     var oldPeoplePhotoFile = Components.classes["@mozilla.org/file/directory_service;1"]
  120.                                        .getService(Components.interfaces.nsIProperties)
  121.                                        .get("ProfD", Components.interfaces.nsIFile);
  122.     oldPeoplePhotoFile.append(OLD_PEOPLE_PHOTO_RDF_FILE);
  123.  
  124.     ctxt.oldPeoplePhotoFile = oldPeoplePhotoFile;
  125.  
  126.     if (oldPeoplePhotoFile.exists())
  127.       ctxt.listener.onUpdate(0, "Migrating photo people");
  128.  
  129.   } else if (oldVersion.substr(0, 3) == "0.9") { // Migration from 0.9.x
  130.     ctxt.listener.onUpdate(0, "Migrating account and media queries");
  131.   }
  132.  
  133.   return { wrappedJSObject: ctxt };
  134. }
  135.  
  136. flockPhotoAPIManager.prototype.finishMigration =
  137. function finishMigration(ctxtWrapper) {
  138. }
  139.  
  140. flockPhotoAPIManager.prototype.doMigrationWork =
  141. function doMigrationWork(ctxtWrapper) {
  142.   var ctxt = ctxtWrapper.wrappedJSObject;
  143.   var oldVersion = ctxt.oldVersion;
  144.  
  145.   if (oldVersion.substr(0, 3) == "0.7") {
  146.     if (!ctxt.oldPeoplePhotoFile.exists()) {
  147.       return false;
  148.     }
  149.  
  150.     if (!ctxt.photoMigrator) {
  151.       ctxt.photoMigrator = this._migratePhotoPeople(ctxt);
  152.     }
  153.     if (ctxt.photoMigrator.next()) {
  154.       ctxt.photoMigrator = null;
  155.     }
  156.  
  157.     return Boolean(ctxt.photoMigrator);
  158.   } else if (oldVersion.substr(0, 3) == "0.9") {
  159.     this._migrate09xPhotoAccount();
  160.     this._migrateMediaQueries(oldVersion);
  161.     return false;
  162.   }
  163. }
  164.  
  165. // josh changed latestSeq property of mediaqueries from
  166. // an int to a string - we need to migrate this type change
  167. flockPhotoAPIManager.prototype._migrateMediaQueries =
  168. function _migrateMediaQueries(aOldVersion)
  169. {
  170.   this.mLogger.info("._migrateMediaQueries()\n");
  171.   var dataFileDS = RDFS.GetDataSource("rdf:flock-favorites");
  172.  
  173.   // get all media query rdf nodes
  174.   var mqResource = RDFS.GetResource(FLOCK_NS+"CoopType");
  175.   var mqLiteral = RDFS.GetResource(NC_NS+"MediaQuery");
  176.   var mediaQueries = dataFileDS.GetSources(mqResource, mqLiteral, true);
  177.  
  178.   function getTargetInt(aSource, aArcName) {
  179.     var target = dataFileDS.GetTarget(aSource, RDFS.GetResource(FLOCK_NS+aArcName), true);
  180.     target instanceof Components.interfaces.nsIRDFInt;
  181.     return target;
  182.   };
  183.   function getTargetLiteral(aSource, aArcName) {
  184.     var target = dataFileDS.GetTarget(aSource,
  185.                                       RDFS.GetResource(FLOCK_NS+aArcName),
  186.                                       true);
  187.     target instanceof Components.interfaces.nsIRDFLiteral;
  188.     return target;
  189.   };
  190.   function getTargetDate(aSource, aArcName) {
  191.     var target = dataFileDS.GetTarget(aSource,
  192.                                       RDFS.GetResource(FLOCK_NS+aArcName),
  193.                                       true);
  194.     target instanceof Components.interfaces.nsIRDFDate;
  195.     return target;
  196.   };
  197.  
  198.   while (mediaQueries && mediaQueries.hasMoreElements()) {
  199.     var mediaQuery = mediaQueries.getNext();
  200.     mediaQuery.QueryInterface(Components.interfaces.nsIRDFResource);
  201.     var subject = RDFS.GetResource(mediaQuery.Value);
  202.     // For latestSeq, replace old int value with string if migrating
  203.     // from 0.9.0.x
  204.     if (aOldVersion.substr(0, 5) == "0.9.0") {
  205.       var latestSeqInt = getTargetInt(mediaQuery, "latestSeq");
  206.       var predicate = RDFS.GetResource(FLOCK_NS+"latestSeq");
  207.       var latestSeqString = RDFS.GetLiteral(latestSeqInt.Value);
  208.       dataFileDS.Change(subject, predicate, latestSeqInt, latestSeqString);
  209.     }
  210.  
  211.     // Migrate the favicon url
  212.     var iconPred = RDFS.GetResource(FLOCK_NS+"favicon"); 
  213.     var faviconLit = getTargetLiteral(mediaQuery, "favicon");
  214.     var svcShortNameLit = getTargetLiteral(mediaQuery, "svc");
  215.     var icon = this.getAPIFromShortname(svcShortNameLit.Value).icon;
  216.     var iconLit = RDFS.GetLiteral(icon);
  217.     // Don't overwrite binary hardcoded favicons
  218.     if (faviconLit.Value.match(/^chrome:/)) {
  219.       dataFileDS.Change(subject, iconPred, faviconLit, iconLit);
  220.     }
  221.  
  222.     var isPollable = getTargetLiteral(mediaQuery, "isPollable");
  223.     if (isPollable.Value == "true") {
  224.       // Force refresh on media query
  225.       dataFileDS.Change(subject,
  226.                         RDFS.GetResource(FLOCK_NS + "nextRefresh"),
  227.                         getTargetDate(mediaQuery, "nextRefresh"),
  228.                         RDFS.GetDateLiteral((new Date()).getTime()*1000));
  229.     }
  230.   }
  231. }
  232.  
  233. flockPhotoAPIManager.prototype._migrate09xPhotoAccount =
  234. function flockPhotoAPIManager__migrate09xPhotoAccount()
  235. {
  236.   this.mLogger.info("._migrate09xPhotoAccount()");
  237.   
  238.   var acUtils = Cc["@flock.com/account-utils;1"]
  239.                    .getService(Ci.flockIAccountUtils);
  240.   var _coopfile = "chrome://flock/content/common/load-faves-coop.js";
  241.   var _coop = Cc["@flock.com/singleton;1"]
  242.                 .getService(Ci.flockISingleton)
  243.                 .getSingleton(_coopfile)
  244.                 .wrappedJSObject;
  245.   var accounts = acUtils.getAccountsByInterface("flockIMediaWebService");
  246.   while (accounts.hasMoreElements()) {
  247.     var account = accounts.getNext();
  248.     account = account.QueryInterface(Ci.flockIWebServiceAccount);
  249.     this.mLogger.debug("_migrate09xPhotoAccount -- migrating " + account.urn);
  250.     var c_acct = _coop.get(account.urn);
  251.     var service = Cc[c_acct.serviceId].getService(Ci.flockIWebService);
  252.     service.migrateAccount(c_acct.accountId, c_acct.name);
  253.   }
  254. }
  255. flockPhotoAPIManager.prototype._migratePhotoPeople =
  256. function _migratePhotoPeople(ctxt)
  257. {
  258.   // Load old datasource
  259.   var oldPhotoPeepsDS = RDFS.GetDataSourceBlocking(IOS.newFileURI(ctxt.oldPeoplePhotoFile).spec);
  260.   var acctsRoot = RDFS.GetResource(LIST_ACCOUNTS_ROOT);
  261.   var streamsRoot = RDFS.GetResource("urn:flock:people:photo:lists:watched");
  262.   var getValue = function (aSource, aArcName) {
  263.     var target = oldPhotoPeepsDS.GetTarget(aSource, RDFS.GetResource(FLOCK_NS+aArcName), true);
  264.     target instanceof Components.interfaces.nsIRDFLiteral;
  265.     return target.Value;
  266.   };
  267.  
  268.   // Migrate accounts
  269.   var accounts = RDFCU.MakeSeq(oldPhotoPeepsDS, acctsRoot).GetElements(); 
  270.   while (accounts && accounts.hasMoreElements()) {
  271.     var account = accounts.getNext();
  272.     account.QueryInterface(Components.interfaces.nsIRDFResource);
  273.     var username = getValue(account, "username");
  274.     var apiShortName = getValue(account, "apiShortName");
  275.     var id = getValue(account, "id");
  276.     var api = this.getAPIFromShortname(apiShortName);
  277.     api.migrateAccount(id, username);
  278.   }
  279.  
  280.   // Load new datasource
  281.   var _coop = Components.classes["@flock.com/singleton;1"]
  282.                         .getService(Components.interfaces.flockISingleton)
  283.                         .getSingleton("chrome://flock/content/common/load-faves-coop.js")
  284.                         .wrappedJSObject;
  285.   var mediaFavesURN = MEDIA_FAVES_ROOT;
  286.   var mediaFaves = _coop.get(mediaFavesURN);
  287.   if (!mediaFaves) {
  288.     mediaFaves = new _coop.Folder(mediaFavesURN);
  289.     _coop.favorites_root.children.add(mediaFaves);
  290.   }
  291.  
  292.   // Migrate favorite photo streams
  293.   var streams = RDFCU.MakeSeq(oldPhotoPeepsDS, streamsRoot).GetElements();
  294.   while (streams && streams.hasMoreElements()) {
  295.     var stream = streams.getNext();
  296.     stream.QueryInterface(Components.interfaces.nsIRDFResource);
  297.     var apiShortName = getValue(stream, "apiShortName");
  298.     var id = getValue(stream, "id");
  299.     var mediaqueryURN = MEDIA_FAVES_ROOT+apiShortName+":user:"+id+"|username:"+getValue(stream, "username");
  300.     var mediaquery = new _coop.MediaQuery(
  301.       mediaqueryURN,
  302.       {
  303.         serviceId: FLOCK_PHOTO_API_MANAGER_CONTRACTID,
  304.         service: apiShortName,
  305.         favicon: this.getAPIFromShortname(apiShortName).iconUrl,
  306.         query: "user:"+id+"|username:"+getValue(stream, "username"),
  307.         name: getValue(stream, "username"),
  308.         isPollable: true
  309.       }
  310.     );
  311.     mediaFaves.children.add(mediaquery);
  312.   }
  313.  
  314.   // rename the old people photo file
  315.   ctxt.oldPeoplePhotoFile.moveTo(null, OLD_PEOPLE_PHOTO_RDF_FILE_RELIC);
  316.   yield true;
  317. }
  318.  
  319. //////////////////////////////////////////////////////////////////////////////
  320. // Implementation
  321. //////////////////////////////////////////////////////////////////////////////
  322.  
  323. //////////////////////////////////////////////////////////////////////////////
  324.  
  325. flockPhotoAPIManager.prototype.mStates = {};
  326. flockPhotoAPIManager.prototype.mListeners = new Array();
  327.  
  328. flockPhotoAPIManager.prototype.addListener = function(aListener) {
  329.     if (aListener == null) {
  330.         throw  "Listener is null!!";
  331.     }
  332.     this.mListeners.push(aListener);
  333. }
  334.  
  335. flockPhotoAPIManager.prototype.updateStates = function() {
  336.     for(var p in this.mStates) {
  337.         var shortName = p;
  338.         var state = this.mStates[p];
  339.         var api = this.getAPIFromShortname(p);
  340.         var newState = api.authState;
  341.         if(newState != state) {
  342.             this.notify(api);
  343.         }
  344.         this.mStates[p] = newState;
  345.     }
  346. }
  347.  
  348. flockPhotoAPIManager.prototype.notify = function(aService) {
  349.     for(var i=0;i<this.mListeners.length;++i) {
  350.             this.mListeners[i].onAPIStateChange(aService);
  351.     }
  352. }
  353.  
  354. flockPhotoAPIManager.prototype.doLogin = function(aAccountURN) {
  355.     // TODO: MAYBE FIXME (CDC):
  356.     // I changed the param for this function from aShortname to aAccountURN
  357.     // ... but I don't think this is actually called from anywhere so for now
  358.     // I'm gonna just throw NOT_IMPLEMENTED instead of fixing it to handle the
  359.     // new parameter.  If it breaks things, I owe the first person who notices
  360.     // it a bubble tea.
  361.     throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  362.  
  363.     var inst = this;
  364.     var myListener = {
  365.         onAuth: function() {
  366.             inst.notify(service);
  367.             inst.setDefaultService(service.shortName);
  368.         },
  369.         onError: function(aError) {
  370.            for (var i=0; i < inst.mListeners.length; i++) {
  371.                aError.QueryInterface(Components.interfaces.flockIError);
  372.                try {
  373.                     var theError = {};
  374.                     theError.errorCode = aError.errorCode;
  375.                     theError.serviceErrorCode = aError.serviceErrorCode;
  376.                     theError.serviceErrorString = aError.serviceErrorString;
  377.                     theError.errorString = aError.errorString;
  378.                     inst.mListeners[i].onError(theError);
  379.                } catch (ex) {
  380.                     this.mLogger.error(ex);
  381.                }
  382.             }
  383.             
  384.         },
  385.     }
  386.     var service = this.getAPIFromShortname(aShortname);
  387.     service.login(null, myListener);
  388.     this.notify(service);
  389. }
  390.  
  391. flockPhotoAPIManager.prototype.doLogout = function(aShortname) {
  392.     var service = this.getAPIFromShortname(aShortname);
  393.     service.logout();
  394.  
  395.     // TODO - maybe change the default service to something that is enabled ???
  396.     
  397.     this.notify(service);
  398. }
  399.  
  400.  
  401. flockPhotoAPIManager.prototype.removeListener = function(aListener) {
  402.     for(var i=0;i<this.mListeners.length;++i) {
  403.         if(aListener==this.mListeners[i]) {
  404.             this.mListeners.splice(i,1);
  405.             break;
  406.         }
  407.     }
  408. }
  409.  
  410.  
  411. flockPhotoAPIManager.prototype.getAPIFromShortname = function(aShortname) {
  412.   // JMC - Special case, me!
  413.   if (aShortname == "preview") {
  414.     return this;
  415.   }
  416.  
  417.   var catman = Cc["@mozilla.org/categorymanager;1"]
  418.                .getService(Ci.nsICategoryManager);
  419.   var webServices = [];
  420.   var e = catman.enumerateCategory("flockMediaProvider");
  421.   while (e.hasMoreElements()) {
  422.     var entry = e.getNext().QueryInterface(Ci.nsISupportsCString);
  423.     if (!entry) {
  424.       continue;
  425.     }
  426.     var contractID = catman.getCategoryEntry("flockMediaProvider", entry.data);
  427.     var svc = Cc[contractID].getService(Ci.flockIWebService);
  428.     if (svc.shortName == aShortname) {
  429.       return svc;
  430.     }
  431.   }
  432.   return null;
  433. }
  434.  
  435.  
  436. function loadLibraryFromSpec(aSpec)
  437. {
  438.   var loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
  439.                          .getService(Components.interfaces.mozIJSSubScriptLoader);
  440.   loader.loadSubScript(aSpec);
  441. }
  442.  
  443. loadLibraryFromSpec("chrome://flock/content/common/aggregate.js");
  444.  
  445. flockPhotoAPIManager.prototype.supportsSearch = function (aQuery) {
  446.   return false;
  447. }
  448.  
  449. flockPhotoAPIManager.prototype.search = function (aListener, aQuery, aCount, aPage) {
  450.   // JMC - PQ of all mediaquery objects with new stuff
  451.   var faves_coop = Components.classes['@flock.com/singleton;1']
  452.                              .getService(Components.interfaces.flockISingleton)
  453.                              .getSingleton("chrome://flock/content/common/load-faves-coop.js")
  454.                              .wrappedJSObject;
  455.                              
  456.   // JMC - If it's a page =1 request, perform the search
  457.   // Otherwise, return the next batch from the cached results
  458.   if (aPage > 1) {
  459.     aListener.onSearchResult(this.myUnseenEnum);
  460.   } else {                            
  461.     var mediaQueries = faves_coop.MediaQuery.find({hasUnseenItems: true});
  462.     var streams = [];
  463.     for (x in mediaQueries) {
  464.       var mediaquery = mediaQueries[x];
  465.       streams.push(mediaquery.children.enumerate());
  466.     }
  467.     var aggUnSeen = filterStream( aggregatePhotoStreams( streams ), function(item) {
  468.          return item && item.unseen;
  469.        });
  470.     var inst = this;   
  471.     this.myUnseenEnum = {
  472.       hasMoreElements : function() {
  473.           return this.mEnum.hasMoreElements();
  474.       },
  475.       getNext : function() {
  476.         var child = this.mEnum.getNext();
  477.         // JMC - Get the real flockIPhoto from the coop obj.
  478.         var svcName = child.getParent().service;
  479.         return inst.getAPIFromShortname(svcName).getPhotoFromRDFNode(child.id());
  480.       },
  481.     };
  482.     this.myUnseenEnum.mEnum = aggUnSeen;   
  483.     aListener.onSearchResult(this.myUnseenEnum);
  484.   }
  485. }
  486.  
  487. flockPhotoAPIManager.prototype.__defineGetter__("services", function () {
  488.     var ar = new Array();
  489.     var catman = Cc["@mozilla.org/categorymanager;1"]
  490.                  .getService(Ci.nsICategoryManager);
  491.     var webServices = [];
  492.     var e = catman.enumerateCategory("flockMediaProvider");
  493.     while (e.hasMoreElements()) {
  494.       var entry = e.getNext().QueryInterface(Ci.nsISupportsCString);
  495.       if (!entry) {
  496.         continue;
  497.       }
  498.       var contractID = catman.getCategoryEntry("flockMediaProvider", entry.data);
  499.       var svc = Cc[contractID]
  500.                 .getService(Ci.flockIWebService);
  501.       if (svc instanceof Ci.flockIMediaWebService) {
  502.         ar.push(svc);
  503.       }
  504.     }
  505.  
  506.     var rval = {
  507.         getNext: function() {
  508.             var rval = ar.shift();
  509.             return rval;
  510.         },
  511.         hasMoreElements: function() {
  512.             return (ar.length>0);
  513.         },
  514.     }
  515.     return rval;
  516. })
  517.  
  518. flockPhotoAPIManager.prototype.getPreferredServices = function() {
  519.     try {
  520.         var pref = this.mPrefService.getCharPref("flock.photo.preferredServices");
  521.         var ar = pref.split(",");
  522.         var rval = [];
  523.         for(var i=0;i<ar.length;++i) {
  524.             if(ar[i].length) rval.push(ar[i]); 
  525.         }
  526.         return rval;
  527.     } catch(e) {
  528.         //debug(e);
  529.         debug("getPreferredServices: No preferred services found, returning an empty set\n");
  530.         return [];
  531.     }
  532. }
  533.  
  534. flockPhotoAPIManager.prototype.setPreferredServices = function(aArray) {
  535.     var val = aArray.join(",");
  536.     var pref = this.mPrefService.setCharPref("flock.photo.preferredServices", val);
  537. }
  538.  
  539. flockPhotoAPIManager.prototype.getDefaultService = function() {
  540.     try {
  541.         var pref = this.mPrefService.getCharPref("flock.photo.lastService");
  542.         if (pref && pref.length)
  543.             return this.getAPIFromShortname(pref);
  544.     } catch(e) {
  545.         return null;
  546.     }
  547. }
  548.  
  549. flockPhotoAPIManager.prototype.setDefaultService = function(aShortname) {
  550.     try {
  551.         this.mPrefService.setCharPref("flock.photo.lastService", aShortname);
  552.     } catch(e) {
  553.         debug(e);
  554.     }
  555. }
  556.  
  557. flockPhotoAPIManager.prototype.__defineGetter__('preferredServices', function () { 
  558. try {
  559.     var inst = this;
  560.     var ar = this.getPreferredServices();
  561.     var rval = {
  562.         getNext: function() {
  563.             var shortName = ar.shift();
  564.             var api = inst.getAPIFromShortname(shortName);
  565.             return api;
  566.         },
  567.         hasMoreElements: function() {
  568.             return (ar.length>0);
  569.         },
  570.     }
  571.     return rval;
  572.     } catch(e) {
  573.         debug(e);
  574.     }
  575. })
  576.  
  577.  
  578. flockPhotoAPIManager.prototype.__defineGetter__("hasNewMedia", function () {
  579.   var faves_coop = Components.classes['@flock.com/singleton;1']
  580.                              .getService(Components.interfaces.flockISingleton)
  581.                              .getSingleton("chrome://flock/content/common/load-faves-coop.js")
  582.                              .wrappedJSObject;
  583.   var mediaFavsFolder = faves_coop.get(MEDIA_FAVES_ROOT);
  584.   return mediaFavsFolder.hasUnseenItems;
  585. })
  586.  
  587. // This method has three responsibilities,
  588. // and two entry points.
  589. // First, it ensures that the mediaquery 
  590. // contains the most recent 5 RichPhoto items.
  591. // Second, it ensures that RichPhoto items that are being
  592. // Freshly created are marked as "unseen".
  593. // Finally, it does NOT mark them as unseen on first refresh.
  594.  
  595. // The enum is required to be sorted, newest first
  596.  
  597. flockPhotoAPIManager.prototype.processPhotoStream =
  598. function photoAPI_processPhotoStream(aUrn, aEnum, bMarkUnseen)
  599. {
  600.   var MAX_MEDIAQUERY_CHILDREN = 5;
  601.   var faves_coop = Components.classes['@flock.com/singleton;1']
  602.                               .getService(Components.interfaces.flockISingleton)
  603.                               .getSingleton("chrome://flock/content/common/load-faves-coop.js")
  604.                               .wrappedJSObject;
  605.                               
  606.   var myStream = faves_coop.get(aUrn);
  607.   if (!myStream || this.processingQueue[aUrn]) {
  608.     return; 
  609.   }  
  610.   this.processingQueue[aUrn] = true;
  611.   
  612.   // JMC - Pruning run, save to destroy children later
  613.   var myStreamEnum = myStream.children.enumerateBackwards();
  614.   var oldChildren = [];
  615.   while (myStreamEnum.hasMoreElements())
  616.   {
  617.     var child = myStreamEnum.getNext();
  618.     if (child) {
  619.       oldChildren.push(child);
  620.       myStream.children.remove(child);
  621.     }
  622.   }
  623.  
  624.   var biggestSeq = myStream.latestSeq;
  625.   var latestDate = myStream.latestDate;
  626.   var inst = this;
  627.   var photoIndex = 1;
  628.   var newContent = false;
  629.   function myWorker(shouldYield) {
  630.     while (aEnum.hasMoreElements() && photoIndex <= MAX_MEDIAQUERY_CHILDREN) {
  631.       var photo = aEnum.getNext();
  632.       photo.QueryInterface(Components.interfaces.flockIPhoto);
  633.       
  634.       // For streams with no uploadDate, we use now. Is that good?
  635.       var photoDate = (photo.uploadDate) ? new Date(parseInt(photo.uploadDate)) : new Date();
  636.       if (photoDate > latestDate) {
  637.         latestDate = photoDate;
  638.         biggestSeq = photo.id;
  639.       }
  640.  
  641.       var urn = 'urn:flock:item:photo:'+myStream.service+':'+photo.id;
  642.       var photoFav;
  643.       photoFav = faves_coop.get(urn);
  644.       var alreadyIn = (photoFav != null);
  645.       if (!alreadyIn) {
  646.         newContent = true;
  647.       }
  648.         
  649.       // if it doesn't exist, create it
  650.       if (!photoFav) {
  651.         photoFav = new faves_coop.RichPhoto(urn, {
  652.           datevalue: photoDate,
  653.           URL: photo.webPageUrl,
  654.           thumbnail: photo.thumbnail,
  655.           cachedThumbnail: "",
  656.           is_public: photo.is_public,
  657.           is_video: photo.is_video,
  658.           midSizePhoto: photo.midSizePhoto,
  659.           name: photo.title,
  660.           username: photo.username,
  661.           userid: photo.userid,
  662.           photoid: photo.id,
  663.           largeSizePhoto: photo.largeSizePhoto
  664.         });
  665.         // add new photostream to myworld service queue
  666.         var myworldService = Cc[MYWORLD_SERVICE_CONTRACTID].getService(Ci.flockIMyworldService);
  667.         myworldService.queueResource(urn);
  668.       }
  669.  
  670.       myStream.children.addOnce(photoFav);
  671.       
  672.       if (!myStream.firstRefresh && !alreadyIn && bMarkUnseen) {
  673.         photoFav.unseen = true;
  674.       }
  675.       photoIndex++;
  676.       if (shouldYield()) yield;
  677.     }
  678.     
  679.     // if the old children aren't new children, and they're orphans, then destroy them
  680.     for(var i =0; i < oldChildren.length; i++)
  681.     {
  682.       var child = oldChildren[i];
  683.       if (child.getParents().length < 1)
  684.       {
  685.         child.destroy();
  686.       }
  687.     }
  688.     // If we added a newer photo
  689.     if (newContent) {
  690.       myStream.latestSeq = biggestSeq;
  691.       myStream.latestDate = latestDate;
  692.       if (!myStream.firstRefresh && bMarkUnseen) {
  693.         // JMC - Shouldn't this propagate?
  694.         myStream.hasUnseenItems = true;
  695.         var parent = myStream.getParent();
  696.         if (parent)
  697.         {
  698.           parent.hasUnseenItems = true;
  699.         }
  700.       }
  701.     }
  702.     myStream.firstRefresh = false;                         
  703.     if (myStream.latestDate != latestDate) {
  704.       myStream.latestDate = latestDate;
  705.     }
  706.  
  707.     inst.processingQueue[aUrn] = false;
  708.   }
  709.   schedule(0.3, 30, myWorker);
  710. }
  711.  
  712. flockPhotoAPIManager.prototype.refresh = function(aUrn, aListener) {
  713.  
  714.   var faves_coop = Components.classes['@flock.com/singleton;1']
  715.                              .getService(Components.interfaces.flockISingleton)
  716.                              .getSingleton("chrome://flock/content/common/load-faves-coop.js")
  717.                              .wrappedJSObject;
  718.   var photoAPIManager = this;
  719.   var listener = function(aStream, aSearchListener) {
  720.     this.mStream = aStream;
  721.     this.mListener = aSearchListener;
  722.   }
  723.   
  724.   var myStream = faves_coop.get(aUrn);
  725.   if (!myStream) {
  726.     aListener.onError();
  727.     return; 
  728.   }
  729.  
  730.   listener.prototype.onSearchResult = function(aEnum) {
  731.     photoAPIManager.processPhotoStream(aUrn, aEnum, true);
  732.     this.mListener.onResult(null, null);
  733.   };
  734.  
  735.   listener.prototype.onError = function(aError) {
  736.       photoAPIManager.mLogger.error(aError);
  737.       if(this.mListener) this.mListener.onError(aError);
  738.   };
  739.  
  740.   var theApi = this.getAPIFromShortname(myStream.service);
  741.   if (!theApi) {
  742.     aListener.onResult(null, null);
  743.     return; 
  744.   }
  745.  
  746.   theApi.search(new listener(myStream, aListener), myStream.query, 5, null);
  747.   return;
  748.  
  749. }
  750.  
  751.  
  752. flockPhotoAPIManager.prototype.flags = nsIClassInfo.SINGLETON;
  753. flockPhotoAPIManager.prototype.classDescription = "Flock Photo People Service";
  754. flockPhotoAPIManager.prototype.getInterfaces = function (count) {
  755.     var interfaceList = [Components.interfaces.flockISocialWebService, flockIPhotoAPIManager, flockIPollingService,
  756.                          flockIMigratable, nsIClassInfo];
  757.     count.value = interfaceList.length;
  758.     return interfaceList;
  759. }
  760.  
  761. flockPhotoAPIManager.prototype.getHelperForLanguage = function (count) {return null;}
  762.  
  763. // JMC - Making this a social photo service for the preview stream
  764. flockPhotoAPIManager.prototype.QueryInterface =
  765. function (iid) {
  766.     if (!iid.equals(Components.interfaces.flockISocialWebService) &&
  767.         !iid.equals(flockIPhotoAPIManager) && 
  768.         !iid.equals(flockIPollingService) &&
  769.         !iid.equals(flockIMigratable) &&
  770.         !iid.equals(nsIClassInfo) &&
  771.         !iid.equals(nsISupports))
  772.         throw Components.results.NS_ERROR_NO_INTERFACE;
  773.     return this;
  774. }
  775.  
  776. // Module implementation
  777. var PhotoPeopleModule = new Object();
  778.  
  779. PhotoPeopleModule.registerSelf =
  780. function (compMgr, fileSpec, location, type)
  781. {
  782.     compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  783.  
  784.     compMgr.registerFactoryLocation(FLOCK_PHOTO_API_MANAGER_CID, 
  785.                                     "Flock Photo API Manager JS Component",
  786.                                     FLOCK_PHOTO_API_MANAGER_CONTRACTID, 
  787.                                     fileSpec, 
  788.                                     location,
  789.                                     type);
  790.     var catman = Components.classes['@mozilla.org/categorymanager;1']
  791.       .getService(Components.interfaces.nsICategoryManager);
  792.      
  793.     catman.addCategoryEntry('flockMigratable', 'photoservice', FLOCK_PHOTO_API_MANAGER_CONTRACTID, true, true);
  794. }
  795.  
  796. PhotoPeopleModule.getClassObject =
  797. function (compMgr, cid, iid) {
  798.     if (!cid.equals(FLOCK_PHOTO_API_MANAGER_CID))
  799.         throw Components.results.NS_ERROR_NO_INTERFACE;
  800.     
  801.     if (!iid.equals(Components.interfaces.nsIFactory))
  802.         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  803.     
  804.     return PhotoAPIManagerFactory;
  805. }
  806.  
  807. PhotoPeopleModule.canUnload =
  808. function(compMgr)
  809. {
  810.     return true;
  811. }
  812.     
  813. /* factory object */
  814. var PhotoAPIManagerFactory = new Object();
  815.  
  816. PhotoAPIManagerFactory.createInstance =
  817. function (outer, iid) {
  818.     if (outer != null)
  819.         throw Components.results.NS_ERROR_NO_AGGREGATION;
  820.  
  821.     return (new flockPhotoAPIManager()).QueryInterface(iid);
  822. }
  823.  
  824. /* entrypoint */
  825. function NSGetModule(compMgr, fileSpec) {
  826.     return PhotoPeopleModule;
  827. }
  828.  
  829.  
  830.  
  831. // a utility for async scheduling:
  832.  
  833. // cheap scheduler for javascript tasks
  834.  
  835. // aSlice is the maximum timeslice in seconds. 0.5 or less is a good value
  836.  
  837. // aPercent is the percent of wall-clock time that should be spent executing
  838. // the task - eg: 20 = one fifth of the time
  839.  
  840. // aTask is defined to be a generator taking an function that evaluates
  841. // to true if it should yield like:
  842. // function task (should_yield) {
  843. //   while (some_condition) {
  844. //     do some work...
  845. //     if (should_yield()) yield;
  846. //   }
  847. // }
  848. // NOTE: should_yield() is fairly cheap but in a tight inner loop it might
  849. //       make sense to call it every N cycles
  850.  
  851. var timers = [];
  852. function schedule (aSlice, aPercent, aTask) {
  853.   // convert percent to ratio
  854.   var ratio = aPercent / 100.0;
  855.  
  856.   // convert slice to milliseconds
  857.   var slice = aSlice * 1000.0;
  858.  
  859.   function milliseconds () { return (new Date ()).getTime (); }
  860.   var wall_start = milliseconds ();
  861.   var process_time = 0;
  862.  
  863.    
  864.   var process_start = 0;
  865.   function should_yield () {
  866.     var now = milliseconds ();
  867.     var so_far = now - process_start;
  868.     var wall_time = now - wall_start;
  869.     return so_far >= slice || ((wall_time * ratio) < (process_time + so_far));
  870.   }
  871.  
  872.   var generator = aTask (should_yield);
  873.  
  874.   var timer = Components.classes['@mozilla.org/timer;1']
  875.     .createInstance (Components.interfaces.nsITimer);
  876.   var callback = {
  877.     notify: function () {
  878.       var wall_time = milliseconds () - wall_start;
  879.  
  880.       if ((wall_time * ratio) < process_time) {
  881.         // if we've spent more time than we're supposed to
  882.         // don't run this cycle
  883.       }
  884.  
  885.       process_start = milliseconds ();
  886.       try {
  887.         generator.next ();
  888.       } catch (err if err instanceof StopIteration) {
  889.         // the task is complete
  890.         timer.cancel ();
  891.         for (var i=0; i<timers.length; i++) { 
  892.           if (timers[i] == timer) {
  893.             timers.splice (i,1);
  894.           }
  895.         }
  896.         timer = null;
  897.       }
  898.       process_time += (milliseconds () - process_start);
  899.     }
  900.   }
  901.  
  902.   timer.initWithCallback (callback, Math.round (slice/ratio),
  903.       Components.interfaces.nsITimer.TYPE_REPEATING_SLACK)
  904.   timers.push (timer);
  905. }
  906.